﻿using System.Collections.Generic;
using UnityEngine;

namespace GameToolkit
{

    public class CullingManager
    {
        private static CullingManager sInstance;
        public static CullingManager Instance
        {
            get
            {
                if (sInstance == null)
                    sInstance = new CullingManager();
                return sInstance;
            }
        }

        public static void Release()
        {
            if (sInstance != null)
            {
                for (int j = 0; j < sInstance.mBehaviours.Count; j++)
                {
                    sInstance.mBehaviours[j].ClearCullingTarget();
                }
                sInstance.mBehaviours.Clear();
                sInstance.mCullingTargets.Clear();
                sInstance = null;
            }
        }

        HashSet<ICullingBounds> mCullingTargets;
        List<CullingCamera> mBehaviours;
        List<ICullEvent> mCullEvents;

        private CullingManager()
        {
            mCullingTargets = new HashSet<ICullingBounds>();// new AvlTree<CullingBounds>((x) => x.GetInstanceID());
            mBehaviours = new List<CullingCamera>(5);
            mCullEvents = new List<ICullEvent>(32);
        }

        public void GetCullingTargets(CullingCamera behaviour)
        {
            if (!mBehaviours.Contains(behaviour))
            {
                behaviour.ClearCullingTarget();
                mBehaviours.Add(behaviour);
#if UNITY_EDITOR
                var isPrefab = IsPrefabStagae(behaviour);
#endif
                foreach (var cull in mCullingTargets)
                {
#if UNITY_EDITOR
                    if (isPrefab != IsPrefabStagae(cull))
                        continue;
#endif
                    if (((1 << cull.Layer) & behaviour.CullingLayers) != 0)
                    {
                        behaviour.AppendCullingTarget(cull);
                    }
                }
                foreach (var cull in mCullEvents)
                {
#if UNITY_EDITOR
                    if (isPrefab != IsPrefabStagae(cull))
                        continue;
#endif
                    if (((1 << cull.Layer) & behaviour.CullingLayers) != 0)
                        behaviour.AppendCullingEvent(cull);
                }
            }
        }

        public void ReleaseCullingTargets(CullingCamera behaviour)
        {
            behaviour.ClearCullingTarget();
            mBehaviours.Remove(behaviour);
        }

        public void AddCullEvent(ICullEvent action)
        {
            if (!mCullEvents.Contains(action))
            {
                mCullEvents.Add(action);
#if UNITY_EDITOR
                var isPrefab = IsPrefabStagae(action);
#endif
                for (int i = mBehaviours.Count - 1; i >= 0; i--)
                {
                    var culler = mBehaviours[i];
                    if (culler == null)
                    {
                        mBehaviours.RemoveAt(i);
                        continue;
                    }
#if UNITY_EDITOR
                    if (isPrefab != IsPrefabStagae(culler))
                        continue;
#endif
                    if ((culler.CullingLayers & (1 << action.Layer)) != 0)
                    {
                        culler.AddCullingEvent(action);
                    }
                }
            }
        }

        public void RemoveCullEvent(ICullEvent action)
        {
            if (mCullEvents.Remove(action))
            {
                for (int i = mBehaviours.Count - 1; i >= 0; i--)
                {
                    var culler = mBehaviours[i];
                    if (culler == null)
                    {
                        mBehaviours.RemoveAt(i);
                        continue;
                    }
                    if ((culler.CullingLayers & (1 << action.Layer)) != 0)
                    {
                        culler.RemovePreCullAction(action);
                    }
                }
            }
        }

#if UNITY_EDITOR
        public static bool IsPrefabStagae(object obj)
        {
            if (obj is IPrefabStageInstance)
                return ((IPrefabStageInstance)obj).IsPrefabStage;
            if (obj is GameObject)
                return GlobalUtil.IsPrefabStage((GameObject)obj);
            if (obj is Component)
                return ((Component)obj) != null && GlobalUtil.IsPrefabStage(((Component)obj).gameObject);
            return false;
        }
#endif

        public void AddCullData(ICullingBounds target)
        {
            if (mCullingTargets.Add(target))
            {
#if UNITY_EDITOR
                var isPrefabStage = IsPrefabStagae(target);
#endif
                for (int i = mBehaviours.Count - 1; i >= 0; i--)
                {
                    var culler = mBehaviours[i];
                    if (culler == null)
                    {
                        mBehaviours.RemoveAt(i);
                        continue;
                    }
#if UNITY_EDITOR
                    if (IsPrefabStagae(culler) != isPrefabStage)
                    {
                        continue;
                    }
#endif
                    if ((culler.CullingLayers & (1 << target.Layer)) != 0)
                    {
                        culler.AddCullingTarget(target);
                    }
                }
            }
        }

        public void RemoveCullData(ICullingBounds target)
        {
            if (mCullingTargets.Remove(target))
            {
                //for (int i = mBehaviours.Count - 1; i >= 0; i--)
                //{
                //    var culler = mBehaviours[i];
                //    if (culler == null)
                //    {
                //        mBehaviours.RemoveAt(i);
                //        continue;
                //    }
                //    if ((culler.CullingLayers & (1 << target.Layer)) != 0)
                //    {
                //        culler.RemoveCullingTarget(target);
                //    }
                //}
            }
        }

    }
}